extern crate crypto;
extern crate serde_json;

// use rserialize::base64::{STANDARD, ToBase64, FromBase64};
// use rserialize::hex::FromHex;
use chrono::offset::Utc;
use chrono::DateTime;
use encoding::all::GBK;
use encoding::all::UTF_8;
use encoding::{DecoderTrap, Encoding};
use std::error::Error;
use std::time::SystemTime;

use std::sync::mpsc::Sender;
use std::sync::Arc;
use std::sync::Mutex;

use std::fs;
use std::fs::File;
use std::io::{Read, Write};
use std::path::Path;

use super::config::home_output;
use log::error;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::process::{Command, Stdio};
use std::str;

use colored::Colorize;
use uuid::Uuid;
use regex::Regex;

pub type S<T> = Sender<T>;
// pub type S<T> = Sender<String>;

pub type OutErr<T> = Result<T, Box<dyn Error>>;

pub type Safe<T> = Arc<Mutex<T>>;
pub type SafeSender = Safe<Sender<String>>;
pub const SEP_PRE: &str = "->->->->->->->=>=>=>=>";

pub struct OutputObject{
    path: String,
    status: String,
    hit:String,
    blocks: HashMap<String,String>,
}



pub trait StringFile {
    fn get_content_list(&self) -> Vec<String>;
    fn to_output_object(&self) -> Option<OutputObject>;
    fn uuid_to_object(&self) -> Option<OutputObject>;
    fn uuid_to_new_object(&self) -> String;
    fn open(&self) -> Option<String>;
    fn grep(&self, key:&str) -> (Vec<i64>,Vec<String>);
    fn file_replace(&self, key:&str, replace:&str) -> bool;
    fn replace_block(&self, key:&str, replace:&str) -> bool;
    fn to_dict(&self, key:&str) -> HashMap<String, String>;
}

pub trait StringHost {
    fn get_host_list(&self) -> Vec<String>;
    fn idr(&self) -> Vec<String>;
}

pub trait StringSplit {
    // add code here
    fn split_once(&self, sep: &str) -> (String, String);
    fn split_twice(&self, sep: &str) -> (String, String, String);
}

impl OutputObject {
    pub fn insert(&mut self, key:&str, val:&str){
        self.blocks.insert(key.to_string(), val.to_string());
    }

    pub fn brd_task_to_object(brd_key:&str)-> bool{
        if brd_key.contains(" || "){
            // log::info!("--- brd append > ");
            let (_, m_id, _) = brd_key.split_twice(" || ");
            if let Some(mut o) = m_id.uuid_to_object(){
                o.append("sub_uuids", brd_key.trim());
                o.save();
                true
            }else{
                false
            }
        }else{
            false
        }
    }

    pub fn append(&mut self, key:&str, val:&str){
        let mut old_val:String = match self.blocks.get(key){
            Some(a) => a.to_string(),
            _ => {
                log::info!("append a new key:{}", key.yellow());
                String::new()
            }
        };
        if old_val.ends_with("\n\n"){
            old_val = old_val.trim().to_string();   
        }
        old_val.push_str(&format!("\n{}",val));
        
        self.blocks.insert(key.to_string(), old_val);
    }

    pub fn get(&self, key:&str) -> Option<String>{
        if self.blocks.contains_key(key){
            Some(self.blocks.get(key).unwrap().to_string())
        }else{
            None
        }
        
    }
    pub fn keys(&self) -> Vec<String>{
        self.blocks.keys().map(|x|x.to_string()).collect()
    }
    
    pub fn sub_uuids(&self) -> Vec<String>{
        if let Some(v) = self.get("sub_uuids"){
            v.split("\n").map(|x| x.to_string()).collect()
        }else{
            vec![]
        }
    }

    pub fn uuids(&self) -> Vec<String>{
        let mut v:Vec<String> = Vec::new();
        for k in self.blocks.keys(){
            match Uuid::parse_str(k){
                Ok(_) => {
                    v.push(k.to_string())
                },
                _ => {}
            }
        }
        v
        
    }

    pub fn load(uuid:&str, key:&str) -> Option<String>{
        if let Some(obj) = uuid.uuid_to_object(){
            if obj.blocks.contains_key(key){
                Some(obj.blocks.get(key).unwrap().to_string())
            }else{
                None
            }
        }else{
            None
        }
    }
    pub fn filter_not_with(key:&str, val:&str) -> Vec<String>{
        let mut uuids:Vec<String> = Vec::new();
        for uuid in list_uuid_output(){
            if let Some(obj) = uuid.uuid_to_object(){
                if let Some(v) = obj.blocks.get(key){
                    if v != val{
                        uuids.push(uuid.clone());
                    }
                }
            }
        }
        uuids
    }
    pub fn filter_with(key:&str, val:&str) -> Vec<String>{
        let mut uuids:Vec<String> = Vec::new();
        for uuid in list_uuid_output(){
            if let Some(obj) = uuid.uuid_to_object(){
                if let Some(v) = obj.blocks.get(key){
                    if v == val{
                        uuids.push(uuid.clone());
                    }
                }
            }
        }
        uuids
    }

    pub fn uuids_with(key:&str) -> HashMap<String,String>{
        let mut uuids:HashMap<String,String> = HashMap::new();
        for uuid in list_uuid_output(){
            if let Some(obj) = uuid.uuid_to_object(){
                if let Some(v) =  obj.blocks.get(key){
                        uuids.insert(uuid.clone(), v.clone());
                }
            }
        }
        uuids
    }
    pub fn save(&mut self){
        if let Ok(mut f) = File::create(&self.path){
            for (k,v) in &self.blocks{
                if k.len() == 0 {continue}
                // log::info!("{} -> {}", k.yellow());
                let _ = write!(f, "{}{}\n",SEP_PRE, k);
                let _ = write!(f, "{}\n", v);
            }
        }
    }
    
}

impl<'a> StringFile for &'a str {
    fn uuid_to_object(&self) -> Option<OutputObject>{
        let output = home_output();
        let output_path = Path::new(&output);
        let output_path_file = output_path.join(self);
        if output_path_file.exists(){
            output_path_file.to_str().unwrap().to_output_object()
        }else{
            None
        }
    }

    fn uuid_to_new_object(&self) -> String{
        let output = home_output();
        let output_path = Path::new(&output);
        let output_path_file = output_path.join(self);
        if output_path_file.exists(){
            output_path_file.to_str().unwrap().to_string()
        }else{
            if let Ok(mut f) = File::create(output_path_file){
                write!(f, "{}status\n", SEP_PRE).unwrap();
                write!(f, "{}\n", "Running").unwrap();
                write!(f, "{}content\n", SEP_PRE).unwrap();
                self.to_string()
            }else{
                self.to_string()
            }
        }
    }
    fn open(&self) -> Option<String>{
        let p = Path::new(self);
        if p.exists(){
            if let Ok(mut f) = File::open(p){
                let mut buf = String::new();
                let mut bytes_buf:Vec<u8> = vec![];
                if let Ok(_) = f.read_to_end(&mut bytes_buf){
                    match UTF_8.decode(&bytes_buf, DecoderTrap::Ignore) {
                        Ok(s) => buf.push_str(&s),
                        _ => {
                            let s = GBK.decode(&bytes_buf, DecoderTrap::Ignore).unwrap();
                            buf.push_str(&s);
                        }
                    };
                }
                
                Some(buf)
            }else{
                None
            }
        }else{
            None
        }
    }

    fn replace_block(&self, key:&str, replace:&str) -> bool{
        if let Some(mut obj) = self.to_output_object(){
            obj.insert(key, replace);
            obj.save();
            return true;
        }
        false
    }

    fn grep(&self, key:&str) -> (Vec<i64>,Vec<String>){
        let mut string_blocks:Vec<String> = vec![];
        let mut string_blocks_line:Vec<i64> = vec![];
        if let Some(buf) = self.open(){
            if let Ok(_re) = Regex::new(key){
                for (no, line) in buf.split("\n").enumerate(){
                    if let Some(_) = _re.captures(line){
                        string_blocks.push(line.to_string());
                        string_blocks_line.push(no as i64);
                    }
                }
            }else{
                log::error!("regexg key error: {}", key.red().bold());
                
            };
            (string_blocks_line, string_blocks)
        }else{
            (string_blocks_line, string_blocks)
        }
    }


    fn file_replace(&self, key:&str, to:&str) -> bool{
        if let Some(buf) = self.open(){
            if let Ok(_re) = Regex::new(key){
                let new_buf = _re.replace_all(&buf, to);
                if let Ok(mut f) = File::create(self){
                    if let Ok(_) = f.write_all(new_buf.as_bytes()){
                        return true;
                    }
                }
            }else{
                log::error!("regexg key error: {}", key.red().bold());
                
            };
            false
        }else{

            false
        }
    }

    fn to_dict(&self, key:&str) -> HashMap<String, String>{
        let mut dict : HashMap<String, String> = HashMap::new();
        if let Some(buf) = self.open(){
            // log::info!("Raw {} ---|", buf.green());
            if let Ok(_re) = Regex::new(key){
                let mut tmp_buf = String::new();
                let mut tmp_key = String::new();
                for l in buf.split("\n"){
                    if let Some(c) = _re.captures(l){
                        let new_tmp_key = c.get(1).unwrap().as_str().to_string().replace(SEP_PRE,"");
                        // log::info!("key {} found", new_tmp_key.green());
                        if tmp_buf.len() != 0 && tmp_key.len() > 0{
                            dict.insert(tmp_key.clone().trim().to_string(), tmp_buf.clone().trim().to_string());
                            tmp_buf = "".to_string();
                        }
                        tmp_key = new_tmp_key.trim().to_string();
                    }else{
                        // if l.trim().len() > 0{
                            
                        // }
                        tmp_buf.push_str(&format!("{}\n", l.trim()));
                        
                    }
                }
                if tmp_buf.len() != 0 {
                    dict.insert(tmp_key.trim().to_string(), tmp_buf.clone());
                    // tmp_buf = format!("{}","");
                }
            }
        }
        dict
    }

    fn to_output_object(&self) -> Option<OutputObject>{
        let h = self.to_dict(r"^->->->->->->->=>=>=>=>(\w[\-\w]+)");
        if h.len() >0{
            Some(OutputObject{
                path:self.to_string(),
                hit: match h.get("hit"){
                    Some(a) => a.to_string(),
                    _ => "".to_string()
                },
                  
                status: match h.get("status"){
                    Some(a) => a.to_string(),
                    _ => "".to_string()
                },
                blocks:h.clone(),
            })
        }else{
            None
        }
        
    }

    fn get_content_list(&self) -> Vec<String> {
        let mut out: Vec<String> = vec![];
        let mut buf: String = String::new();
        if Path::new(self).exists() {
            let mut fp = match fs::File::open(self) {
                Ok(e) => e,
                Err(_) => {
                    return out;
                }
            };
            match fp.read_to_string(&mut buf) {
                Ok(_) => {}
                Err(e) => {
                    error!("read file to list failed reason :{}", e);
                    return out;
                }
            };
        } else {
            return out;
        }
        let _ = buf
            .split("\n")
            .map(|a| {
                out.push(a.to_string());
            })
            .collect::<Vec<_>>();
        out
    }
}
impl<'a> StringHost for &'a str {
    fn get_host_list(&self) -> Vec<String> {
        let tmp = self.get_content_list();
        tmp.iter()
            .map(|a| {
                let one = if !a.starts_with("http://") {
                    format!("http://{}", a)
                } else {
                    a.to_string()
                };
                one
            })
            .collect::<Vec<String>>()
    }

    fn idr(&self) -> Vec<String> {
        let one: Vec<String> = vec![];
        one
    }
}

impl StringHost for String {
    fn get_host_list(&self) -> Vec<String> {
        let tmp = self.as_str().get_content_list();
        tmp.iter()
            .map(|a| {
                let one = if !a.starts_with("http://") {
                    format!("http://{}", a)
                } else {
                    a.to_string()
                };
                one
            })
            .collect::<Vec<String>>()
    }

    fn idr(&self) -> Vec<String> {
        let one: Vec<String> = vec![];
        one
    }
}

impl<'a> StringSplit for &'a str {
    
    fn split_once(&self, sep: &str) -> (String, String) {
        // let o = sep.chars().position(|x|x==');
        let mut splitter = self.splitn(2, sep);
        let first = match splitter.next() {
            Some(e) => e,
            _ => "",
        };
        let second_c: Vec<&str> = splitter.collect();
        let second = second_c.join(sep);
        (first.to_string(), second.to_string())
    }
    fn split_twice(&self, sep: &str) -> (String, String, String) {
        let mut splitter = self.splitn(3, sep);
        let first = match splitter.next() {
            Some(e) => e,
            _ => "",
        };
        let second = match splitter.next() {
            Some(e) => e,
            _ => "",
        };
        let threed_c: Vec<&str> = splitter.collect();
        let third = threed_c.join(sep);
        // let third =match splitter.next(){
        //     Some(e) => e,
        //     _ => {
        //         ""
        //     }
        // };
        (first.to_string(), second.to_string(), third.to_string())
    }
}
impl StringFile for String{
    fn uuid_to_new_object(&self) -> String{
        self.as_str().uuid_to_new_object()
    }
    
    fn get_content_list(&self) -> Vec<String>{
        self.as_str().get_content_list()
    }

    fn to_output_object(&self) -> Option<OutputObject>{
        self.as_str().to_output_object()
    }
    fn open(&self) -> Option<String> {
        self.as_str().open()
    }
    fn grep(&self, key:&str) -> (Vec<i64>,Vec<String>){
        self.as_str().grep(key)
    }
    fn file_replace(&self, key:&str, replace:&str) -> bool{
        self.as_str().file_replace(key, replace)
    }
    fn replace_block(&self, key:&str, replace:&str) -> bool{
        self.as_str().replace_block(key, replace)
    }
    fn to_dict(&self, key:&str) -> HashMap<String, String>{
        self.as_str().to_dict(key)
    }
    fn uuid_to_object(&self) -> Option<OutputObject>{
        self.as_str().uuid_to_object()
    }
}
impl StringSplit for String {
    fn split_once(&self, sep: &str) -> (String, String) {
        let mut splitter = self.splitn(2, sep);
        let first = match splitter.next() {
            Some(e) => e,
            _ => "",
        };
        let second = match splitter.next() {
            Some(e) => e,
            _ => "",
        };
        (first.to_string(), second.to_string())
    }

    fn split_twice(&self, sep: &str) -> (String, String, String) {
        let mut splitter = self.splitn(3, sep);
        let first = match splitter.next() {
            Some(e) => e,
            _ => "",
        };
        let second = match splitter.next() {
            Some(e) => e,
            _ => "",
        };
        let third = match splitter.next() {
            Some(e) => e,
            _ => "",
        };
        (first.to_string(), second.to_string(), third.to_string())
    }
}

pub fn to_safe<T>(t: T) -> Safe<T> {
    Arc::new(Mutex::new(t))
}

pub fn to_static<T>(t: T) -> &'static T {
    Box::leak(Box::new(t))
}

#[macro_export]
macro_rules! try_safe_use {
    ($_self:ident.$name:ident) => {
        let $name = $_self.$name.clone();
    };
    ($expr:ident) => {
        let $expr = $expr.clone();
    };
}

pub fn now() -> String {
    let system_time = SystemTime::now();
    let datetime: DateTime<Utc> = system_time.into();
    format!("{}", datetime.format("%Y-%m-%d-%T"))
}

pub fn append_to_file(main_id: &str, sub_id: &str, out: &[u8]) -> String {
    let output = home_output();
    let output_path = Path::new(&output);
    let output_path_file = output_path.join(main_id);
    let mut output_file = if !output_path_file.exists() {
        fs::File::create(&output_path_file).expect("can not creat4e output uuid file")
    } else {
        std::fs::OpenOptions::new()
            .append(true)
            .open(&output_path_file)
            .expect("can not create output uuid file.")
    };

    output_file
        .write_all(format!("{}{}", SEP_PRE, sub_id).as_bytes())
        .expect("write to file");
    output_file
        .write_all(out)
        .expect("write to uuid file error ");
    output_path_file.as_path().to_str().unwrap().to_string()
}

pub fn output_to_file(id: &str, out: &[u8]) -> String {
    let output = home_output();
    let output_path = Path::new(&output);
    let output_path_file = output_path.join(id);
    let mut output_file =
        fs::File::create(&output_path_file).expect("can not create output uuid file.");
    output_file
        .write_all(out)
        .expect("write to uuid file error ");
    output_path_file.as_path().to_str().unwrap().to_string()
}

pub fn list_uuid_output() -> Vec<String> {
    let output = home_output();
    let mut li: Vec<String> = Vec::new();
    for entry in fs::read_dir(output).expect("list dir error") {
        let en = entry.expect("list entry error");
        let name = en.file_name().to_str().unwrap().to_string();
        match Uuid::parse_str(&name) {
            Ok(_) => {
                li.push(name);
            }
            Err(_) => {}
        }
    }
    li
}

pub fn list_brd_uuid_output() -> HashMap<&'static str,String>{
    let mut v :HashMap<&'static str,String> = HashMap::new();
    for uuid in list_uuid_output(){
        if let Some(o) = uuid.uuid_to_object(){
            let _ = o.sub_uuids().iter().map(|x|{
                v.insert(to_static(x.to_string()) , "wait".to_string());
            }).collect::<Vec<_>>();
        }
    }
    v
}

pub fn load_uuid_output() -> HashMap<&'static str, Value> {
    let mut old_map: HashMap<&'static str, Value> = HashMap::new();
    for uuid in list_uuid_output(){
        if let Some(mut obj) = uuid.uuid_to_object(){
            if let Some(status) = obj.get("status"){
                old_map.insert(to_static(uuid), json!(status));
            }else{
                obj.insert("status", "Running");
                obj.save();
                old_map.insert(to_static(uuid), json!("Running"));
            }
        }
    }
    old_map
}


pub fn clear_uuid_output(){
    // let mut old_map: HashMap<&'static str, Value> = HashMap::new();
    for uuid in list_uuid_output(){
        if let Some(obj) = uuid.uuid_to_object(){
            if let Ok(_) = fs::remove_file(&obj.path){
                log::info!("clear: {}", obj.path.red().on_green())
            }else{
                log::info!("clear: {} [x]", obj.path.green().on_red())
            }
        }
    }
}


pub fn get_uuid_ouput(uuid: &str) -> String {
    let output = home_output();
    let output_f = Path::new(&output).join(uuid);
    let mut buf = String::new();
    let mut f: fs::File;
    let mut bytes_buf: Vec<u8> = vec![];
    if output_f.exists() {
        f = fs::File::open(&output_f).expect("no uuid file to read ");
        f.read_to_end(&mut bytes_buf).expect("read bytes failed");
        match UTF_8.decode(&bytes_buf, DecoderTrap::Ignore) {
            Ok(s) => buf.push_str(&s),
            _ => {
                let s = GBK.decode(&bytes_buf, DecoderTrap::Ignore).unwrap();
                buf.push_str(&s);
            }
        };
    }
    buf
}

fn std_redirect(dst: Option<String>) -> Option<(fs::File, fs::File)> {
    let _stdout: fs::File;
    let _stderr: fs::File;
    if let Some(out_file) = dst {
        let p = Path::new(&out_file);
        if p.parent().unwrap().exists() {
            _stdout = fs::OpenOptions::new().append(true).open(p).expect("create this file error");
            // _stdout = fs::File::create(p).expect("create this file error");
            _stderr = _stdout.try_clone().expect("copy stderr error ");
            return Some((_stdout, _stderr));
        }
    }
    None
}
pub fn daemon_start(cmd: &str, output: &str) {
    log::info!("run:{}", cmd);
    if !cfg!(target_os = "windows") {
        let old_p = std::path::Path::new("/tmp/mutask.pid");
        if old_p.exists() {
            let mut old_pid_file = fs::File::open(old_p).unwrap();
            let mut b = String::new();
            old_pid_file.read_to_string(&mut b).unwrap();
            Command::new("sh")
                .arg("-c")
                .arg(format!("kill -9 {}", &b.trim()))
                .output()
                .expect("failed to execute process");
            log::info!("kill old daemon. fist");
        }

        let (stdout, stderr) = if Path::new(output).parent().unwrap().exists() {
            (
                fs::File::create(output).unwrap(),
                fs::File::create(output).unwrap(),
            )
        } else {
            (
                fs::File::create("/tmp/mutask.log").unwrap(),
                fs::File::create("/tmp/mutask.log").unwrap(),
            )
        };

        let child = Command::new("sh")
            .arg("-c")
            .arg(format!("{}", cmd))
            .stdout(Stdio::from(stdout))
            .stderr(Stdio::from(stderr))
            .spawn()
            .expect("failed to execute process");
        // .wait_with_output().expect("failed wait output");
        let mut pid_file = fs::File::create(old_p).unwrap();
        pid_file
            .write(format!("{}", child.id()).as_bytes())
            .unwrap();
        println!("run pid:{}", child.id());
    }
}

pub fn shell(
    cmd_string: String,
    id_s: String,
    sep_s: String,
    output_file: Option<String>,
) -> OutErr<String> {
    let mut buf = String::new();
    let id = &id_s;
    let cmd = &cmd_string;
    let sep = &sep_s;
    buf.push_str(id);
    buf.push_str(sep);

    if let Some((stdout, stderr)) = std_redirect(output_file) {
        if cfg!(target_os = "windows") {
            Command::new("cmd")
                .arg("/C")
                .arg(format!("{}", cmd))
                .stdout(Stdio::from(stdout))
                .stderr(Stdio::from(stderr))
                .spawn()
                .expect("failed to execute process")
                .wait_with_output()
                .expect("failed wait output")
        } else {
            Command::new("sh")
                .arg("-c")
                .arg(format!("{}", cmd))
                .stdout(Stdio::from(stdout))
                .stderr(Stdio::from(stderr))
                .spawn()
                .expect("failed to execute process")
                .wait_with_output()
                .expect("failed wait output")
        };
        return Ok(format!("{}", id));
    }

    let output = if cfg!(target_os = "windows") {
        Command::new("cmd")
            .arg("/C")
            .arg(format!("{}", cmd))
            .output()
            .expect("failed to execute process")
    } else {
        Command::new("sh")
            .arg("-c")
            .arg(format!("{}", cmd))
            .output()
            .expect("failed to execute process")
    };

    if !output.status.success() {
        buf.push_str(match str::from_utf8(&output.stderr) {
            Ok(e) => e,
            _ => "include un utf8 charset",
        });
    }
    buf.push_str(match str::from_utf8(&output.stdout) {
        Ok(e) => e,
        _ => "include un utf8 charset",
    });
    Ok(buf)
}
